home *** CD-ROM | disk | FTP | other *** search
/ SGI Hot Mix 17 / Hot Mix 17.iso / HM17_SGI / research / lib / map_set.pro < prev    next >
Text File  |  1997-07-08  |  39KB  |  981 lines

  1. ; $Id: map_set.pro,v 1.64 1997/04/08 21:29:26 dave Exp $
  2. ;
  3. ; Copyright (c) 1993-1997, Research Systems, Inc.  All rights reserved.
  4. ;    Unauthorized reproduction prohibited.
  5.  
  6. PRO map_struct_append, In_struct, Name, Value, SUPERCEDE = super
  7. ;
  8. ; Append/Replace the tagname Name, with a given value to the In_Struct.
  9. ; If SUPERCEDE is set, and a tag with the given Name already exists, 
  10. ;    replace its value.
  11. ; If SUPERCEDE is NOT set, and the given tag exists, do nothing.
  12. ;
  13.  
  14. ntags=N_TAGS(In_struct)
  15.  
  16. if ntags eq 0 then begin        ;If In_struct is undef, just return the tag
  17.     In_struct = CREATE_STRUCT(name, value)
  18. endif else begin
  19.     tnames=tag_names(IN_STRUCT)
  20.     index=where(tnames eq NAME, count)
  21.     if count eq 0 then $        ;No match, append
  22.       In_Struct = CREATE_STRUCT(in_struct, name, value)    $
  23.     else if keyword_set(super) then $
  24.       in_struct.(index[0]) = value ;Overwrite value?
  25.                                 ;Otherwise, tag is already there, don't add
  26. endelse
  27. end
  28.  
  29.  
  30.  
  31. PRO MAP_STRUCT_MERGE, orig, add, SUPERCEDE = super
  32. ; Add the keywords in the struct add to the original struct orig.
  33. ; ADD must be either undefined or defined as a structure.
  34. ; If a given tag name is present in both structures:
  35. ;   If SUPERCEDE is set, then the value of add.tag replaces the
  36. ;   original value.  If SUPERCEDE is not set, the original value
  37. ;   remains unchanged.
  38.  
  39. if n_elements(orig) eq 0 then begin
  40.     if n_elements(add) ne 0 then orig = add
  41.     return
  42. endif
  43.  
  44. otag = tag_names(orig)
  45. atag = tag_names(add)
  46. for i=0, n_elements(atag)-1 do begin ;Add the new ones
  47.     match = where(otag eq atag[i], count)
  48.     if count eq 0 then $
  49.       orig = create_struct(orig, atag[i], add.(i)) $ ;Disjoint tag name, add it
  50.     else if keyword_set(super) then $ ;Got a match, if super is set, overwrite.
  51.       orig.(match[0]) = add.(i) ;Common, add if set
  52. endfor
  53. end
  54.  
  55.  
  56. FUNCTION map_point_valid, lon, lat, u, v
  57. ; Return 1 if the point at geo-coordinates (lon, lat) is mappable,
  58. ;    0 if not.
  59. ; Mappable means that the point is transformable, and is not clipped
  60. ;   by the mapping pipeline.
  61. ; Input: lon, lat = input coordinates in degrees.
  62. ; Output: u, v = uv coordinates if point is mappable.  The contents of
  63. ;   u and v are undefined if the function returns 0, indicating that the
  64. ;   point is not mappable.
  65.  
  66. u = 0.0 & v = 0.0
  67. p = convert_coord(lon, lat, /DATA, /TO_NORM) ;Cvt to normalized coords
  68. if finite(p(0)) eq 0 then return, 0
  69. u = (p(0) - !x.s(0)) / !x.s(1)  ;To UV coords
  70. v = (p(1) - !y.s(0)) / !y.s(1)
  71.  
  72. clat = cos(lat * !dtor)         ;Cvt lon lat to cartesian 3D coords
  73. xyz = [ clat * cos(lon * !dtor), clat * sin(lon * !dtor), sin(lat * !dtor)]
  74.  
  75. p = !map.pipeline
  76. s = size(p)
  77. for i=0, s(2)-1 do begin        ;# of stages
  78.     case p(0,i) of
  79.         0 : return, 1           ;0 = END, means we're successful.
  80.         1 : dummy = 0           ;Ignore splits
  81.         2 : if total(xyz * p(1:3,i)) + p(4,i) lt 0 then $ ;Outside clip plane?
  82.           return, 0
  83.         3 : dummy = 0           ;Ignore transforms
  84.         4 : if u lt p(1,i) or v lt p(2,i) or $ ;Within UV bounding box?
  85.           u gt p(3,i) or v gt p(4,i) then return, 0
  86.     endcase
  87. endfor
  88. return, 1                       ;Should never hit here.
  89. end                             ;map_point_valid
  90.  
  91.  
  92.  
  93. PRO map_set_ll_box
  94.  
  95. ;Try to determine a lon/lat range from the existing parameters, and
  96. ;save in !MAP.LL_BOX = [latmin, lonmin, latmax, lonmax].
  97. ; If we can't reliably determine the lat/lon limits then the
  98. ; coorresponding min and max are both set to zero.
  99.  
  100. ; If either pole is mappable, then all longitudes are mappable
  101. north = map_point_valid(0, 90)
  102. south = map_point_valid(0, -90)
  103. dateline = (north + south eq 0) and map_point_valid(-180., !map.p0lat) and $
  104.   (map_point_valid(0., !map.p0lat) eq 0) ; Dateline visible
  105.  
  106. if north and south then return  ;The whole globe is visible
  107.  
  108. if north then begin
  109.     lonmax = 180. & lonmin = -180. ;All longitudes are visible
  110.     latmax = 90. & latmin = 90.
  111. endif else if south then begin
  112.     lonmax = 180. & lonmin = -180.
  113.     latmax = -90. & latmin = -90.
  114. endif else begin
  115.     if dateline then begin
  116.         lonmax = 0. & lonmin = 360.
  117.     endif else begin
  118.         lonmax = -180. & lonmin = 180.
  119.     endelse
  120.     latmin = 90. & latmax = -90.
  121. endelse
  122.  
  123. box = !map.uv_box
  124.  
  125. ; Edges are in order: bottom, right, top, left.
  126.  
  127. sidex = [0, 2, 2, 0 ,0]         ;X index of UV corner
  128. sidey = [1, 1, 3, 3, 1]         ;Y index
  129. uvcen = (box([0,1]) + box([2,3]))/2.
  130. del = 8                         ;# of intervals
  131. found = bytarr(4)
  132.  
  133. ; March along the 4 uv edges of the plot, keeping the range in
  134. ; geo-coordinates.
  135.  
  136. for iside = 0, 3 do begin
  137.     s0 = box([sidex(iside), sidey(iside)]) ;Start of edge
  138.     s1 = box([sidex(iside+1), sidey(iside+1)]) ;End of edge
  139.     for i = 0, del-1 do begin
  140.         s2 = s0 + (s1-s0) * (float(i)/del) ;UV coordinate
  141.         n2 = s2 * [!x.s(1), !y.s(1)] + [!x.s(0), !y.s(0)] ;Normalized coord
  142.         p = convert_coord(n2(0), n2(1), /NORMAL, /TO_DATA)
  143.         if finite(p(0)) then begin
  144.             if dateline and (p(0) lt 0) then p(0) = p(0) + 360
  145.             lonmax = lonmax > p(0)
  146.             lonmin = lonmin < p(0)
  147.             latmax = latmax > p(1)
  148.             latmin = latmin < p(1)
  149.             found(iside) = 1    ;Found a legit point
  150.         endif
  151.     endfor                      ;for i
  152. endfor                          ;for iside
  153.  
  154. if total(found) eq 4 then begin ;If we didn't values along 4 sides then punt
  155.     !map.ll_box = [latmin, lonmin, latmax, lonmax]
  156. ;    print, !map.ll_box, format='(4f10.2)'
  157. endif
  158. end
  159.  
  160.  
  161.  
  162. PRO map_set_limits, limit, uvrange
  163. ; Limit = 4 or 8 point vector containing lat/lon limits. (input).
  164. ;    WARNING: limit may be changed.
  165. ; uvrange = uv extent of map in form of [umin, vmin, umax, vmax]
  166. ; (output).  Also, on output, limit is converted to the 8 point form.
  167. ;
  168.  
  169. ; Set unit scaling for convert_coord:
  170. !x.s = [0,1]
  171. !y.s = [0,1]
  172.  
  173. if n_elements(limit) eq 4 then begin ;cvt 4 pt to 8 pt limit.
  174. ; ***********        limit = [latmin, lonmin, latmax, lonmax]
  175.     n = 7                       ;Check an NxN grid within the lat/lon limits
  176.     londel = limit[3] - limit[1]
  177.     if londel lt 0 then londel = londel + 360
  178.     lats = findgen(n) * ((limit[2]-limit[0])/(n-1)) + limit[0]
  179.     lons = findgen(n) * (londel / (n-1)) + limit[1]
  180.     j = 0
  181.     for i=0, n^2-1 do begin     ;Check the grid for extrema
  182.         uv = convert_coord(lons[i mod n], lats[i/n], /TO_NORM, /DATA)
  183.         if finite(uv[0]) then begin
  184.             if j ne 0 then begin ;Already got a limit?
  185.                 uvrange[0] = uvrange[0] < uv[0]
  186.                 uvrange[2] = uvrange[2] > uv[0]
  187.                 uvrange[1] = uvrange[1] < uv[1]
  188.                 uvrange[3] = uvrange[3] > uv[1]
  189.             endif else uvrange = uv[[0,1,0,1]]
  190.             j = j + 1
  191.         endif                   ;in range
  192.     endfor
  193.     if j lt 2 then message, 'At least two limit points must be mappable', /INFO
  194. endif else if n_elements(limit) eq 8 then begin
  195. ; 8 point limit in form of:
  196. ;    [latLeft,lonLeft, latTop, lonTop, LatRt, lonRt, LatBot, LonBot]
  197. ;
  198.     for i=0,3 do begin
  199.         uv = convert_coord(limit[i*2+1], limit[i*2], /TO_NORM, /DATA)
  200.         if finite(uv[0]) eq 0 then $
  201.           message, 'Unmappable limit point: ' + $
  202.             string(limit[i*2+1], limit[i*2]), /INFO
  203. ;    Left = uvrange(0)=uv(0); Top = uvrange(3) = uv(1); Right =
  204. ;    uvrange(2) = uv(0); Bot = uvrange(1) = uv(1)
  205.         uvrange[([0,3,2,1])[i]] = uv[([0,1,0,1])[i]]
  206.     endfor
  207. endif else message, 'Map limit must have 4 or 8 points'
  208. end
  209.  
  210.  
  211.  
  212. FUNCTION map_rotxyz, p, rx, ry, rz ;Rotate the vector p(3) counterclockwise
  213. ; about the X, Y, and Z, by the amounts rx, ry, rz, degrees, in order.
  214. ;
  215. p1 = p
  216. dtor = !dpi/ 180.
  217.  
  218. if rx ne 0.0 then begin
  219.     sx = sin(rx * dtor) & cx = cos(rx * dtor)
  220.     t = [[1,0,0],[0,cx, sx], [0, -sx, cx]]
  221.     p1 = t # p1
  222. endif
  223. if ry ne 0.0 then begin
  224.     sy = sin(ry * dtor) & cy = cos(ry * dtor)
  225.     t = [[cy, 0, -sy], [0, 1, 0], [sy, 0, cy]]
  226.     p1 = t # p1
  227. endif
  228. if rz ne 0.0 then begin
  229.     sz = sin(rz * dtor) & cz = cos(rz * dtor)
  230.     t = [[cz, sz, 0], [ -sz, cz, 0], [0,0,1]]
  231.     p1 = t # p1
  232. endif
  233. return, p1
  234. end
  235.  
  236.  
  237. FUNCTION GREAT_CIRCLE, lon0, lat0, lon1, lat1, RADIANS=radians, PRINT=print
  238. ; Return: [sin(c), cos(c), sin(az), cos(az)] from the great circle
  239. ; distance between two points.  c is the great circle angular distance,
  240. ; and az is the azimuth between two points.
  241.  
  242. if KEYWORD_SET(radians) eq 0 then k = !dtor else k = 1.0
  243.  
  244. coslt1 = cos(k*lat1)
  245. sinlt1 = sin(k*lat1)
  246. coslt0 = cos(k*lat0)
  247. sinlt0 = sin(k*lat0)
  248.  
  249. cosl0l1 = cos(k*(lon1-lon0))
  250. sinl0l1 = sin(k*(lon1-lon0))
  251.  
  252. cosc = sinlt0 * sinlt1 + coslt0 * coslt1 * cosl0l1 ;Cos of angle between pnts
  253. sinc = sqrt(1.0 - cosc^2)
  254. if abs(sinc) gt 1.0e-6 then begin 
  255.     cosaz = (coslt0 * sinlt1 - sinlt0*coslt1*cosl0l1) / sinc ;Azmuith
  256.     sinaz = sinl0l1*coslt1/sinc
  257. endif else begin        ;Its antipodal
  258.     cosaz = 1.0
  259.     sinaz = 0.0
  260. endelse
  261.  
  262. if keyword_set(print) then begin
  263.     print, 'Great circle distance: ', acos(cosc) / k
  264.     print, 'Azimuth: ', atan(sinaz, cosaz)/k
  265. ;    print,'sinaz, cosaz = ', sinaz, cosaz
  266. endif
  267. return, [sinc, cosc, sinaz, cosaz]
  268. end
  269.  
  270.  
  271. PRO map_satellite_limit, n, xr, yr
  272. ; Return n or less points, defining the limit of the limb of a satellite
  273. ; projection
  274. a = findgen(n) * (2 * !pi / (n-1))
  275. st = !map.p[0] > 1.0
  276. st = sqrt((st-1.)/(st+1.))    ;Map limit radius for satellite projection
  277. xr = cos(a) * st        ;Taken from Snyder Pg. 175
  278. yr = sin(a) * st
  279. a =  yr * !map.cosr + xr * !map.sinr
  280. xr = xr * !map.cosr - yr * !map.sinr
  281. yr = a
  282. a = yr * !map.p[2]/(!map.p[0]-1.0) + !map.p[3]
  283. good = where(a gt 0.0, count)
  284. if count eq 0 then begin        ;If nothing, return nothing
  285.     trash = temporary(xr) & trash = temporary(yr)
  286.     return
  287. endif
  288. if count lt n then begin        ;Extract & shift so pnts on map are contiguous
  289.     i0 = 0            ;Get 1st good pnt following a bad pnt
  290.     for i=1,count-1 do if good[i-1]+1 ne good[i] then i0 = i
  291.     good = shift(good,-i0)
  292. endif
  293. a = a[good]
  294. xr = xr[good] * !map.p[3] / a
  295. yr = yr[good]/a
  296. end
  297.  
  298.  
  299.  
  300. PRO map_proj_info, iproj, CURRENT=current, $
  301.        UVRANGE=r, WIDTH=meters, NAME=name, CYLINDRICAL=cyl, $
  302.        PROJ_NAMES = proj_names_p, CIRCLE=is_circular, $
  303.        AZIMUTHAL=is_azimuthal, UV_LIMITS = uv_limits, $
  304.        LL_LIMITS = ll_limits
  305. ; Return information about maps in general, or projection iproj.
  306.  
  307. ; Iproj = projection number, Input.  If CURRENT is set, then the
  308. ;     current map projection is returned in Iproj.
  309. ; CURRENT = if set, use current proj number, and return that index in iproj.
  310. ; UVRANGE =  UV limits of the given projection, [umin, vmin, umax, vmax],
  311. ; WIDTH = (output) the width, in meters, of the the map at a scale
  312. ;  of 1:1.  The radius of the earth is used. If this result is 0, the
  313. ;  projection's UV coordinates are already in meters.
  314. ; NAME = the name of the projection.
  315. ; CYLINDRICAL = 1 if the projection is cylindrical or pseudo-cylindrical.
  316. ; PROJ_NAMES = string array containing the names of all the
  317. ;     available projections.
  318. ; UV_LIMITS = returned rectangle of the current map in UV coordinates,
  319. ;    [Umin, Vmin, Umax, Vmax].
  320. ; LL_LIMITS = returned rectangle of the current map in latitude/longitude
  321. ;     coordinates, [Lonmin, Latmin, Lonmax, Latmax].  Note, the
  322. ;     range may not always be available, and the min and max may
  323. ;     both be set to 0.
  324.  
  325. ; Issues for a new projection:
  326. ; UVrange, proj_scale, is_cyl, is_azimuthal, circle, name, horizon, clipping
  327.  
  328. common map_set_common, proj_scale, proj_is_cyl, proj_names, circle, $
  329.   proj_is_azim, user_defined
  330.  
  331. on_error, 2
  332. if n_elements(proj_scale) eq 0 then begin ;Init constant arrays?
  333.     p2 = 2 * !pi
  334.     s8 = 2 * sqrt(8)
  335.     ro = 2 * 2.628
  336.     
  337. ;                St Or LC La Gn  Az Sa  Cy  Me  Mo  Si  Ai  Ha Al Tm  Mi Ro
  338. ;                    1  2  3  4  5   6  7   8   9  10  11  12  13 14 15  16 17
  339.   proj_scale =  [ 0, 4, 2, 4, 4, 4, p2, 1, p2, p2, s8, p2, p2, s8, 4, 0, p2,ro]
  340.   proj_is_cyl = [ 0, 0, 0, 0, 0, 0,  0, 0,  1,  1,  1,  1,  1,  1, 0, 1,  1, 1]
  341.   proj_is_azim= [ 0, 1, 1, 0, 1, 1,  1, 0,  0,  0,  0,  0,  0,  0, 0, 0,  0, 0]
  342.   circle =      [ 0, 1, 1, 0, 1, 1,  1, 1,  0,  0,  1,  0,  1,  1, 0, 0, 0,  0]
  343.   proj_names =  $
  344.     ["", "Stereographic", "Orthographic", "LambertConic", "LambertAzimuthal", $
  345.      "Gnomic", "AzimuthalEquidistant", "Satellite", "Cylindrical", $
  346.      "Mercator", "Mollweide", "Sinusoidal", "Aitoff", "HammerAitoff", $
  347.      "AlbersEqualAreaConic", "TransverseMercator", $
  348.      "MillerCylindrical", "Robinson"]
  349.   user_defined = bytarr(n_elements(proj_names))
  350. endif
  351.  
  352. max_proj = n_elements(proj_names)-1
  353. if arg_present(proj_names_p) then proj_names_p = proj_names
  354. if KEYWORD_SET(current) then iproj = !map.projection ;Get current projection
  355.  
  356. if ARG_PRESENT(uv_limits) then uv_limits = !map.uv_box
  357. if ARG_PRESENT(ll_limits) then ll_limits = !map.ll_box
  358.  
  359. if n_elements(iproj) eq 0 then return ;Rest of params are proj specific
  360.  
  361. if iproj lt 1 or iproj gt max_proj then $
  362.   message, 'Projection number must be within range of 1 to'+ strtrim(max_proj,2)
  363.  
  364. ones = [-1., -1., 1., 1.]
  365. r_earth = 6378206.4d0 ;Earth equatorial radius, meters, ; Clarke 1866 ellipsoid
  366.  
  367. cyl = proj_is_cyl[iproj]
  368. name = proj_names[iproj]
  369. meters = proj_scale[iproj] * r_earth
  370. is_circular = circle[iproj]
  371.  
  372. if arg_present(r) then case iproj of
  373.     1: r = ones                 ;Stereo
  374.     2: r = ones                 ;Ortho
  375.     3: r = 2 * ones             ;Lambert Conic
  376.     4: r = 2 * ones             ;lambert
  377.     5: r = 2 * ones             ;gnomic
  378.     6: r =  !DPI * ones         ;azimuth
  379.     7: if !map.p[1] ne 0 then begin ;Complicated satellite?
  380.         map_satellite_limit, 60, xr, yr
  381.         if n_elements(xr) eq 0 then r = [0,0,0,0] $
  382.         else r = [min(xr, max=maxxr), min(yr, max=maxyr), maxxr, maxyr]
  383.         meters = (r[2]-r[0]) * r_earth
  384.     endif else begin
  385.         st = !map.p[0] > 1.0   ;For satellite
  386.         r = sqrt((st-1)/(st+1)) * ones ;Simple case satellite
  387.         meters = (r[2]-r[0]) * r_earth
  388.     endelse
  389.     8: r = [-1., -.5, 1., .5] * !DPI ;Cylind equidistant
  390.     9: r = [-!DPI, -2.43, !DPI, 2.43] ;Mercator  (+/- 80 degs of lat) ~ 98%
  391.     10: r = sqrt(2.0) * [-2.,-1.,2.,1.] ;Mollweide
  392.     11: r = [-1., -.5, 1., .5] * !DPI ;sinusoidal
  393.     12: r = [-1., -.5, 1., .5] * !DPI ;Aitoff
  394.     13: r = sqrt(2.0) * [-2., -1., 2., 1.] ; Hammer-aitoff
  395.     14: r = 2 * ones            ;Albers Conic
  396.     15: r = 2*!pi * r_earth * ones ;Transverse Mercator, Units = meters.
  397.     16: r = [-!DPI, -1.832, !DPI, 1.832] ;Miller Cylind  (+/- 80 degs of lat)
  398.     17: r = [-2.628, -1.349, 2.628, 1.349] ;Robinson
  399. endcase
  400.  
  401. end
  402.  
  403.  
  404. PRO map_horizon, FILL=fill, NVERTS=n, ZVALUE=zvalue, _EXTRA=Extra
  405. ; Draw/fill the horizon (limb) of a map
  406.  
  407. map_proj_info, /CURRENT, NAME=name, UVRANGE=p, CIRCLE=is_circular
  408.  
  409. r = !map.rotation        ;Our rotation
  410.                                 ;Can't do conics
  411. if name eq "LambertConic" or name eq "AlbersEqualAreaConic" then return
  412. if n_elements(n) le 0 then n = 60 ;# of vertices
  413. a = findgen(n+1) * (2 * !pi / n) ;N angles from 0 to 2 pi
  414.  
  415. if name eq "Satellite" and !map.p[1] ne 0 then begin ;Satellite proj, tilted
  416.     map_satellite_limit, 60, xr, yr
  417.     r = 0.0                     ;No more rotation
  418. endif else if is_circular then begin ;Std circular projection
  419.     xr = (p[2]-p[0])/2. * cos(a)
  420.     yr = (p[3]-p[1])/2. * sin(a)
  421. endif else if name eq "Sinusoidal" then begin ;Sinusoidal
  422. ;    flon = (!map.out(3)-!map.out(2))/360.
  423.     flon = 1.0
  424.     xr = flon * !pi * cos(a)
  425.     yr = fltarr(n+1)
  426.     for i=0,n do case fix(a[i]/(!pi/2)) of
  427.         0:    yr[i] = a[i]
  428.         1:    yr[i] = !pi-a[i]
  429.         2:    yr[i] = !pi-a[i]
  430.         3:    yr[i] = a[i]-2*!pi
  431.         4:    yr[i] = 0.0     ;Last pnt
  432.     endcase
  433. endif else if name eq "Robinson" then begin ;Robinson
  434.     lp = [2.628, 2.625, 2.616, 2.602, 2.582, 2.557, 2.532, 2.478, 2.422, $
  435.           2.356, 2.281, 2.195, 2.099, 1.997, 1.889, 1.769, 1.633, 1.504, 1.399]
  436.     lm = [0., 0.084, 0.167, 0.251, 0.334, 0.418, 0.502, 0.586, 0.669, 0.752, $
  437.           0.833, 0.913, 0.991, 1.066, 1.138, 1.206, 1.267, 1.317, 1.349]
  438.     xr = [lp, -reverse(lp), -lp, reverse(lp)]
  439.     yr = [lm, reverse(lm), -lm, -reverse(lm)]
  440. endif else if name eq "UserDefined" then begin ;User projection
  441.  
  442. endif else begin                ;Rectangular
  443.     xr = [p[0], p[2], p[2], p[0], p[0]]
  444.     yr = [p[1], p[1], p[3], p[3], p[1]]
  445. endelse
  446.  
  447. if r ne 0.0 then begin
  448.     t1 = xr * !map.cosr + yr * !map.sinr ;Now rotate by - angle...
  449.     yr =  yr * !map.cosr - xr * !map.sinr
  450.     xr = t1
  451. endif
  452.  
  453. xtsave = !x.type
  454. if n_elements(zvalue) eq 0 THEN zvalue = 0
  455.  
  456. !x.type=0            ;Plot in UV space
  457. if keyword_set(fill) then polyfill, xr, yr, _EXTRA=Extra, NOCLIP=0 $
  458. else plots, xr, yr, zvalue, _EXTRA=Extra, NOCLIP=0
  459. !x.type=xtsave
  460. end
  461.  
  462.  
  463. PRO map_set_split, ADD=add, EQUATOR = equator
  464. ;  Set the splitting hemisphere.  If EQUATOR is set, set the splitting 
  465. ;  point as if the center of projection was on the equator, i.e. use
  466. ;  a latitude of 0.
  467.  
  468. pole = !map.pole[4:6]        ;Locn of pole
  469.  
  470. if keyword_set(equator) then begin ;use lat = 0
  471.     split = [!map.p0lon, 0, -sin(!map.u0), cos(!map.u0), 0.0, 0.0]
  472. endif else begin
  473.     coslat = cos(!map.v0)
  474.     p0 = [ cos(!map.u0) * coslat, sin(!map.u0) * coslat, sin(!map.v0)]
  475.     pln = crossp(p0, pole)
  476.     split=[!map.p0lon, !map.p0lat, pln, 0.0]
  477. endelse
  478.  
  479. noadd = keyword_set(add) eq 0    ;= 0 if we're adding
  480. MAP_CLIP_SET, RESET=noadd, SPLIT=split, TRANSFORM=noadd
  481. end
  482.  
  483.  
  484.  
  485. PRO map_set_clip, pname
  486. ; Set up the default clipping/splitting for the given projection.
  487. ;
  488. r = -1.0            ;Great circle clipping radius...
  489. if pname eq "LambertConic" or pname eq "AlbersEqualAreaConic" then begin
  490.     isign = 2*(!map.p[3] ge 0.0)-1 ;Assume its in one hemisphere
  491. ;    map_set_split, /EQUATOR
  492.     map_set_split
  493.     map_clip_set, CLIP_PLANE=[0,0,isign,0]
  494.     map_clip_set, CLIP_PLANE=[0,0,- isign, .95] ;Towards pole
  495.     
  496. endif else if pname eq 'Mercator' or pname eq 'MillerCylindrical' then begin
  497.     rm = 0.975                  ;Go out 97.5% of the way to the poles
  498.     map_set_split
  499.     map_clip_set, CLIP_PLANE=[-!map.pole[4:6], rm]
  500.     map_clip_set, CLIP_PLANE=[!map.pole[4:6], rm]
  501.     
  502. endif $                         ;Other cylindricals
  503. else if pname eq "Stereographic" or pname eq "Orthographic" then r = -1.0e-5 $
  504. else if pname eq "Satellite" then r = - 1.01/!map.p[0] $
  505. else if pname eq "Gnomic" then r = -0.5 $
  506. else if pname eq "LambertAzimuthal" or pname eq "AzimuthalEquidistant" then $
  507.   r=0.8 $
  508. else if pname eq "TransverseMercator" then r = -0.4 $ ;UTM, a fudge factor.
  509. else begin                      ;If we haven't setup a clip, add splitting
  510.     map_proj_info, /CURRENT, CYLINDRICAL=is_cyl
  511.     if is_cyl then map_set_split
  512. endelse
  513.  
  514. ;    If r is set, set clipping to points on a plane whose normal
  515. ;    passes thru the center (u0,v0), and at a distance of r from the
  516. ;    origin.  r < 0 is the side towards (u0,v0).
  517.  
  518. if r ne -1.0 then $
  519.   map_clip_set, CLIP_PLANE=[cos(!map.u0) * !map.coso, $
  520.                             sin(!map.u0) * !map.coso, !map.sino, r]
  521. map_clip_set, /TRANSFORM
  522. end
  523.  
  524.  
  525.  
  526. ; *****************************************************************
  527. PRO MAP_SET, p0lat, p0lon, rot, $
  528. ;    ********** Projection keywords:
  529.   PROJECTION=proj, $              ;The projection index
  530.   NAME=name, $                    ;The projection name as a string
  531.   STEREOGRAPHIC = stereographic, $
  532.   ORTHOGRAPHIC = orthographic, $
  533.   CONIC = conic, $
  534.   LAMBERT = lambert, $
  535.   GNOMIC = gnomic, $
  536.   AZIMUTHAL = azimuth, $
  537.   SATELLITE = satellite, $      ;Also called general perspective proj
  538.   CYLINDRICAL = cylindrical, $
  539.   MERCATOR = mercator, $
  540.   MILLER_CYLINDRICAL=miller, $
  541.   MOLLWEIDE = mollweide, $     
  542.   SINUSOIDAL = sinusoidal, $
  543.   AITOFF = aitoff, $            ;Original Aitoff projection
  544.   HAMMER = hammer, $            ;This is really the Hammer-Aitoff projection
  545.   ALBERS = albers, $            ;Alber's equal-area conic
  546.   TRANSVERSE_MERCATOR = utm, $  ;Transverse mercator for an ellipsoid
  547.   ROBINSON = robinson, $        ;Robinson projection
  548.                                 ; Also called Gauss-Krueger in Europe.
  549. ; **** Projection specific keywords:
  550. ELLIPSOID = ellips, $           ;Defines the ellipsoid for Transverse mercator.
  551.                                 ;3 elements: a = equatorial radius (meters), e^2
  552.                                 ;= eccentricity squared, k0 = scale on
  553.                                 ;central meridian. e^2 = 2*f-f^2,
  554.                                 ;where f = 1-b/a, b = polar radius
  555.                                 ;Default is the Clarke 1866 ellipsoid, =
  556.                                 ; [6378206.4d0, 0.00676866d0, 0.9996d0]
  557. CENTRAL_AZIMUTH=cent_azim, $    ;Angle of central azimuth (degrees) for:
  558.                                 ; Cylindrical, Mercator, Miller,
  559.                                 ; Mollw, Robinson, and Sinusoidal
  560.                                 ; projections. Default = 0.
  561.                                 ; The pole is placed at an azimuth of
  562.                                 ; CENTRAL_AZIMUTH degrees CCW of north.
  563. STANDARD_PARALLELS = std_p, $   ;One or two standard parallels for conics,
  564.                                 ; One or two element array.
  565. SAT_P = Sat_p, $                ;Satellite parameters: Altitude expressed in
  566.                                 ; units of radii, Omega, and rotation.
  567.                                 ; Rotation may also specified by the
  568.                                 ; rot parameter.
  569. ;    ********** MAP_SET specific keywords:
  570. CLIP=clip, $                    ;Default = do map specific clipping,
  571.                                 ; CLIP=0 to disable
  572. SCALE=scale, $                  ;Construct isotropic map with a given scale.
  573.                                 ; Map Scale is 1:scale, otherwise fit to window
  574. ISOTROPIC = iso, $,             ;If set, make X and Y scales equal
  575. LIMIT = limit, $                ;4 or 8 point lat/lon limit:
  576.                                 ; 4 point: [latmin, lonmin, latmax, lonmax]
  577.                                 ; 8 point: [latLeft,lonLeft, latTop,
  578.                                 ;       lonTop, LatRt, lonRt, LatBot, LonBot]
  579. ;    ********** MAP_SET graphics keywords:
  580. NOERASE=noerase, TITLE=title,$
  581.   ADVANCE = advance, COLOR=color, POSITION = position, $
  582.   NOBORDER=noborder, T3D=t3d, ZVALUE=zvalue, $
  583.   CHARSIZE = charsize, XMARGIN=xmargin, YMARGIN=ymargin, $
  584. ;    ********** MAP_HORIZON keywords:
  585. HORIZON=horizon, E_HORIZON=ehorizon, $ ; E_HORIZON = structure containing
  586.                                 ; extra keywords passed to the
  587.                                 ; map_horizon procedure, e.g.
  588.                                 ; E_HORIZON={FILL:1}
  589. ;    ********** MAP_CONTINENTS keywords:
  590. CONTINENTS = continents, E_CONTINENTS=econt, $ ;E_CONTINENTS = structure
  591.                                 ; containing extra keywords to be
  592.                                 ; passed to
  593.                                 ; map_continents, e.g. E_CONTINENTS={FILL:1}
  594.   USA=usa, HIRES = hires, $
  595.   MLINESTYLE=mlinestyle, MLINETHICK=mlinethick, CON_COLOR=con_color, $
  596.  
  597. ;    ********** MAP_GRID keywords:
  598.   GRID=grid, E_GRID=egrid, $    ;E_GRID = extra keywords structure
  599.   GLINESTYLE=glinestyle, GLINETHICK=glinethick, $
  600.   LABEL=label, LATALIGN=latalign, LATDEL=latdel, LATLAB=latlab, $
  601.   LONALIGN=lonalign, LONDEL=londel, LONLAB=lonlab, $
  602.  
  603. ; Ignored, but here for compatibility:
  604.   WHOLE_MAP=whole_map
  605. ; *****************************************************************
  606. ;+
  607. ;         Limit         =  A four or eight element vector.
  608. ;              If a four element vector, [Latmin, Lonmin, Latmax,
  609. ;                          Lonmax] specifying the boundaries of the region
  610. ;                          to be mapped. (Latmin, Lonmin) and (Latmax,
  611. ;                          Lonmax) are the latitudes and longitudes of
  612. ;                          two diagonal points on the boundary with
  613. ;                          Latmin < Latmax and Lonmin < Lonmax.
  614. ;               For maps that cross the international dateline,
  615. ;               specify west longitudes as angles between
  616. ;               180 and 360.
  617. ;              If an eight element vector: [ lat0, lon0, 
  618. ;                lat1, lon1, lat2, lon2, lat3, lon3] specify
  619. ;                four points on the map which give, respectively,
  620. ;                the location of a point on the left edge, 
  621. ;                top edge, right edge, and bottom edge of
  622. ;                the map extent.
  623. ;-
  624.  
  625.  
  626. ; If the map range can be simply expressed as [latmin, lonmin, latmax,
  627. ; lonmax], then they are contained here.  With many maps, this is
  628. ; impossible. These values are used to speed MAP_GRID and
  629. ; MAP_CONTINENTS, by not drawing elements that are clearly off the
  630. ; map. 
  631. common map_set_common
  632.  
  633. on_error, 2                     ;Return to caller if error.
  634. del = 1.0e-6
  635. dtor = !dpi/ 180.
  636. dpi2 = !dpi/2.
  637. !map.ll_box = 0                 ;Assume no limits
  638.  
  639. if n_elements(proj) then iproj = proj $
  640. else if n_elements(name) then begin ;Name supplied, match it
  641.     map_proj_info, PROJ_NAMES = names ;Available names
  642.     name1 = strcompress(strupcase(name), /REMOVE_ALL) ;Ignore case & blanks
  643.     iproj = (where(name1 eq strupcase(names)))(0) ;Match it
  644.     if iproj lt 1 then message, 'Projection "'+name+'" not available'
  645. endif else if keyword_set(stereographic) then iproj =1 $
  646.   else if keyword_set(orthographic) then iproj =2 $
  647.   else if keyword_set(conic  ) then iproj =3 $
  648.   else if keyword_set(lambert) then iproj =4 $
  649.   else if keyword_set(gnomic ) then iproj =5 $
  650.   else if keyword_set(azimuth) then iproj =6 $
  651.   else if keyword_set(satellite) then iproj =7 $
  652.   else if keyword_set(mercator) then iproj =9 $
  653.   else if keyword_set(mollweide) then iproj = 10 $
  654.   else if keyword_set(sinusoidal) then iproj = 11 $
  655.   else if keyword_set(aitoff) then iproj = 12 $
  656.   else if keyword_set(hammer) then iproj = 13 $
  657.   else if keyword_set(albers) then iproj = 14 $
  658.   else if keyword_set(utm) then iproj = 15 $
  659.   else if keyword_set(miller) then iproj = 16 $
  660.   else if keyword_set(robinson) then iproj = 17 $
  661.   else iproj=8                                     ;Assume cylindrical
  662.  
  663. !map.projection = iproj
  664. map_proj_info, /CURRENT, NAME=pname ;Get symbolic name
  665.  
  666. ; Initial erase?
  667. if !P.multi[0] eq 0 and keyword_set(advance) then erase
  668. if (not KEYWORD_SET(noerase)) and (not KEYWORD_SET(advance)) THEN erase
  669.  
  670.  ; ******** Supply defaults  **********
  671.  
  672. if n_params() lt 3 then rot = 0.0d0
  673. if n_params() lt 2 then p0lon = 0d0
  674. if n_params() lt 1 then p0lat = 0d0
  675.  
  676. if n_elements(cent_azim) le 0 then cent_azim = 0.0
  677. if n_elements(color) eq 0 then color = !p.color  ;Default color
  678. if n_elements(title) eq 0 then title = " "
  679. if n_elements(t3d) le 0 then t3d = 0
  680. if n_elements(zvalue) eq 0 then zvalue = 0.
  681. if n_elements(charsize) eq 0 then charsize = !p.charsize
  682. if charsize le 0.0 then charsize = 1.0
  683. if n_elements(clip) eq 0 then clip = 1 ;default for CLIP is ON
  684.  
  685.                                 ;Hack for CONIC backwards compatibility
  686. if pname eq "LambertConic" and (rot ne 0) and n_elements(std_p) eq 0 then begin
  687.     std_p = [p0lat, p0lon]      ;Two standard parallels
  688.     p0lon1 = rot                ;and rotation is the longitude
  689. endif else p0lon1 = p0lon
  690.  
  691. if abs(p0lat) gt 90.0 then $
  692.    message,'Latitude must be in range of +/- 90 degrees'
  693. if abs(p0lon) gt 360.0 then $
  694.    message,'Longitude must be in range of +/- 360 degrees'
  695.  
  696. u = double(p0lon1)               ;Reduce lon to +/- 180
  697. while u lt -180.0 do u = u + 360.
  698. while u gt 180.0 do u = u - 360.
  699.  
  700. !map.p0lon = u
  701. !map.p0lat = p0lat
  702. !map.u0 = u * dtor
  703. !map.v0 = p0lat * dtor
  704.  
  705. !x.type = 3                     ;New map code
  706. !y.type = 0
  707.  
  708. ;    Simple projection?  (used for (psuedo) cylindricals)
  709. !map.simple = abs(p0lat) le del and abs(cent_azim) le del
  710.  
  711. !map.sino = sin(!map.v0)
  712. !map.coso = cos(!map.v0)
  713.  
  714. !map.rotation = rot
  715. !map.sinr = sin(rot * dtor)
  716. !map.cosr = cos(rot * dtor)
  717.  
  718. ;    Compute position of Pole which is a distance of !PI/2 from
  719. ;    (p0lon, p0lat),    at an azimuth of rot CCW of north.
  720. ;
  721. pole = [0.0, sin(cent_azim * dtor), cos(cent_azim * dtor)]
  722. p2 = map_rotxyz(pole, 0, -p0lat, u)    ;Rotate to put origin at (0,1,0)
  723. plat = asin(p2[2])
  724. cosla = sqrt(1.0-p2[2]^2)
  725. if cosla eq 0.0 then begin         ;On pole?
  726.     plon = 0.0 & sinln = 0.0 & cosln = 1.0
  727.   endif else begin
  728.     plon = atan(p2[1], p2[0])
  729.     sinln = p2[1] / cosla & cosln = p2[0] / cosla
  730.   endelse
  731.  
  732.         ; lon/lat, sin(lat), cos(lat), xyz, Location of pole
  733. !map.pole = [plon, plat, p2[2], cosla, p2]
  734.  
  735. if pname eq "LambertConic" or pname eq "AlbersEqualAreaConic" then begin
  736.     if n_elements(std_p) eq 0 then begin
  737.         if p0lat ne 0 then s = [p0lat, p0lat] else s = [20., 20.]
  738.     endif else if n_elements(std_p) eq 1 then s = std_p[[0,0]] $
  739.     else if n_elements(std_p) eq 2 then s = std_p $
  740.     else message, 'STANDARD_PARALLELS must have 1 or 2 elements'
  741.     s = s * dtor
  742.     if pname eq "LambertConic" then begin
  743.         if ABS(!map.p0lat) eq 90. then $
  744.           message, 'Center of projection may not be pole for conics'
  745.         if s[0] * s[1] lt 0 then $
  746.           message, 'Standard parallels must be in same hemisphere'
  747.         if s[0] eq s[1] then n = sin(s[0]) $
  748.         else n = alog(cos(s[0])/cos(s[1])) / $
  749.           alog(tan(!dpi/4+s[1]/2)/tan(!dpi/4+s[0]/2))
  750.         if n eq 0.0 then message, $
  751.           'Equator is illegal standard parallel for conic'
  752.         F = cos(s[0]) * tan(!dpi/4 + s[0]/2)^n/n
  753.         r0 = F/tan(!dpi/4+!map.v0/2.)^n
  754.         !map.p = [n, F, r0, s]
  755.     endif else begin            ;Albers conic
  756.         n = (sin(s[0]) + sin(s[1]))/2.
  757.         if n eq 0 then begin
  758.             message,'Standard parallels should not be 0', /INFO
  759.             n = 0.5
  760.         endif
  761.         c = cos(s[0])^2 + 2 * n * sin(s[0])
  762.         r0 = sqrt(c - 2*n*sin(!map.v0))/n
  763.         !map.p = [n, c, r0, s]
  764.     endelse
  765. endif
  766.  
  767. if pname eq "Satellite" then begin ;Special params for satellite projection.
  768.     if n_elements(sat_p) eq 0 then !map.p[0] = 2.0 $ ;Altitude in radii
  769.     else !map.p[0] = sat_p[0]
  770.     if n_elements(sat_p) le 1 then omega = 0d0 else omega = dtor * sat_p[1]
  771.                                 ;Save em.  sat(1) = TRUE for simple case (Vertical perspective)
  772.     !map.p[1] = omega
  773.     !map.p[2] = sin(omega)    ;Somega = p[1]
  774.     !map.p[3] = cos(omega)    ;comega
  775.     if n_elements(sat_p) ge 3 then begin ; For backwards compatibility, if
  776. ; Sat_p contains three elements, interpret the 3rd element as the rotation:
  777.         !map.rotation = sat_p[2]
  778.         !map.sinr = sin(sat_p[2] * dtor)
  779.         !map.cosr = cos(sat_p[2] * dtor)
  780.     endif                       ;3 elements
  781. endif
  782.  
  783.  
  784. map_proj_info, /CURRENT, WIDTH=meters, UVRANGE=uvrange, $
  785.   CYLINDRICAL = is_cyl
  786.  
  787. ; Set up cylindrical projections
  788. if pname eq "TransverseMercator" then begin ;Special params for UTM
  789.     if n_elements(ellips) ne 3 then $ ;Default == Clarke 1866 ellipsoid
  790.       ellips = [6378206.4d0, 0.00676866d0, 0.9996d0]
  791. ;   ellips is a 3 element array containing the ellipsoid parameters:
  792. ;           [a, e^2, k0]
  793. ;    a = Equatorial radius, in meters.
  794. ;    b = polar radius, b = a * (1-f), f = 1-b/a
  795. ;    e^2 = eccentricity^2 = 2*f-f^2, where f = flattening.
  796. ;    k0 = scale on central meridian, = 0.9996 for UTM.
  797.     e_2 = ellips[1]/(1.0-ellips[1])
  798. ;        k0      e2       e_2   a         b      m0  m
  799.     !map.p = [ellips[2], ellips[1], e_2, ellips[0], ellips[1], 0., 0.]
  800.     !x.s = [0,1] & !y.s = [0,1] & !x.type = 3 ;Fake coordinate system.
  801.     q = convert_coord(u, p0lat, /data, /to_norm) ;Get m0
  802.     !map.p[5] = !map.p[6]     ;Set m0 from Center of projection
  803.     
  804. endif else if is_cyl then begin ;other Cylindrical or pseudo-cylind proj?
  805. ; I don't know why we can't solve for this angle (the azimuth of (u0,v0)
  806. ; from (xp, yp)) using the law of sines.  This is solving it the hard
  807. ; and long way.....  But it works....
  808.     az = atan(!map.coso * sin(!map.u0-plon), $
  809.               cosla * !map.sino - p2[2] * !map.coso  *cos(!map.u0-plon))
  810.     !map.p[0] = dpi2-az
  811. ;    print, 'Pole: ', plon/dtor, plat /dtor, ', Az=',az / dtor
  812. endif
  813.  
  814.  
  815. uvrange_orig = uvrange          ;Save original UV
  816.  
  817. if is_cyl then begin            ;Adjust for rotation & wrap in cylind projs
  818. ;    Set the x coordinate of the edge in UV space for wrap-around detection.
  819. ;    This is only revelant in (pseudo) cylindrical projections.
  820.     u = uvrange[[0,0,2,2]]    ;The 4 corners.
  821.     v = uvrange[[1,3,1,3]]
  822.     t1 = u * !map.cosr + v * !map.sinr ;Now rotate by - angle...
  823.     v =  v * !map.cosr - u * !map.sinr
  824.     uvrange[0] = min(t1, max=t2)
  825.     uvrange[2] = t2
  826.     uvrange[1] = min(v,  max=t2)
  827.     uvrange[3] = t2
  828. endif
  829.  
  830. if n_elements(limit) gt 0 then begin ;Get UV range.
  831.     if keyword_set(scale) then $
  832.       message, 'Conflicting keywords specified: LIMIT and SCALE', /INFO
  833.     lim = limit                 ;Save old
  834.     if n_elements(lim) eq 4 then !map.ll_box = lim ;Save if 4 point limit
  835.     map_set_limits, lim, uvrange
  836. endif
  837. ; print,'Uvrange=', uvrange
  838.  
  839. ;*****************************************************************
  840.  
  841. if keyword_set(position) then  $
  842.     plot, [0,1], xsty=5, ysty=5, /nodata,    $
  843.     title=title, /noerase, position=position, charsize=charsize $
  844. else begin            ;Position not set
  845.                                 ; Use margins
  846.     if n_elements(xmargin) eq 1 then xmar = [xmargin, xmargin] $
  847.     else if n_elements(xmargin) eq 2 then xmar = xmargin $
  848.     else xmar = [1,1]
  849.     if n_elements(ymargin) eq 1 then ymar = [ymargin, ymargin] $
  850.     else if n_elements(ymargin) eq 2 then ymar = ymargin $
  851.     else ymar = [1,2]
  852.     plot, [0,1], XSTYLE=5, YSTYLE=5, /NODATA, COLOR=color,    $
  853.       XMARGIN= xmar, YMARGIN= ymar, TITLE=title, /NOERASE, $
  854.       CHARSIZE = charsize
  855. endelse
  856.  
  857. !x.type = 3            ;For new maps;
  858.  
  859. if keyword_set(noborder) eq 0 then begin
  860. ; fudge is used to add a bit of spacing between the border and the
  861. ; extent of the map region. fudge of 0.01 indicates that the extra
  862. ; spacing (internal map margin) should be 1% of the original map extent.
  863. ;
  864. ; this extra space is now turned off when the noborder keyword is set
  865. ;
  866.     fudge = 0.01
  867.     s = (uvrange[2]-uvrange[0]) * fudge
  868.     ss = (uvrange[3] - uvrange[1]) * fudge
  869.     uvrange = uvrange + [-s, -ss, s, ss]
  870. endif
  871.  
  872. ;     Figure the size of the drawing area on the screen in normalized units
  873. x_size = !x.window[1]-!x.window[0]
  874. y_size = !y.window[1]-!y.window[0]
  875. x_cen = total(!x.window)/2.     ;Midpoints in X & Y
  876. y_cen = total(!y.window)/2.
  877.  
  878. ; Compute the X and Y scale factors, !x.s(1), !y.s(1):
  879. if keyword_set(scale) then begin ;Absolute scale provided?
  880.                                 ;Absolute size of entire map, meters / uvrange
  881.     if meters ne 0 then $       ;Anything with an inherent scale 
  882.       s = (meters / scale) / (uvrange_orig[2]-uvrange_orig[0])$ ;meters/uv_unit
  883.     else s = 1.0 / scale        ;projections already in meters
  884.     
  885.     ss = !d.x_size / !d.x_px_cm / 100. ;width of window in meters
  886.     !x.s[1] = s / ss
  887.     !y.s[1] = !x.s[1] * !d.x_size / !d.y_size ;Adjust for aspect ratio
  888.             ;Adjust the UV range to include the plotting window  **** 
  889.     t = [x_size / !x.s[1], y_size / !y.s[1]] / 2 ;half the UV range
  890.     c = [uvrange[0]+uvrange[2], uvrange[1]+uvrange[3]]/2 ;UV center
  891. ; Take smallest UV box:
  892.     uvrange = [c[0]-t[0] > uvrange[0], c[1]-t[1] > uvrange[1], $
  893.                c[0]+t[0] < uvrange[2], c[1]+t[1] < uvrange[3]] ;New uv range
  894.  
  895. endif else if keyword_set(iso) then begin
  896.  ; Scale in pixels/uvunit, correct for ISOMETRIC aspect ratio. Use smaller
  897.  ; of X and Y scale factors
  898.     sx = x_size * !d.x_size /(uvrange[2]-uvrange[0]) ;X scale
  899.     sy = y_size * !d.y_size/(uvrange[3]-uvrange[1]) ;Y scale
  900.  
  901.     !x.s[1] = (sx < sy) / !d.x_size ;Set smaller
  902.     !y.s[1] = !x.s[1] * !d.x_size / !d.y_size
  903.     if sx gt sy then $          ;Resize window to fit map area
  904.       !x.window = x_cen + sy/sx/2 * [-x_size, x_size] $
  905.     else !y.window = y_cen + sx/sy/2 * [-y_size, y_size]
  906. endif else begin                ;Scale to fit WINDOW
  907.     !x.s[1] = x_size/(uvrange[2]-uvrange[0])
  908.     !y.s[1] = y_size/(uvrange[3]-uvrange[1])
  909. endelse
  910.  
  911. ; Compute offsets to center UV rectangle in the window
  912. !x.s[0] = -(uvrange[0]+uvrange[2])/2. * !x.s[1] + x_cen
  913. !y.s[0] = -(uvrange[1]+uvrange[3])/2. * !y.s[1] + y_cen
  914.  
  915. map_clip_set,/reset        ;Clear clipping pipeline.
  916.  
  917. if keyword_set(clip) then begin    ;Setup clipping
  918.     map_set_clip, pname         ;Set default clipping?
  919.     if (n_elements(lim) gt 0) or keyword_set(scale) then $ ;Clip UV?
  920.       MAP_CLIP_SET, CLIP_UV = uvrange
  921.                                 ; Set up plotting clip window
  922.     !p.clip([0,2]) = (uvrange([0,2]) * !x.s(1) + !x.s(0)) * !d.x_size
  923.     !p.clip([1,3]) = (uvrange([1,3]) * !y.s(1) + !y.s(0)) * !d.y_size
  924. endif
  925.  
  926. !map.uv_box = uvrange          ;Save UV range
  927.  
  928. if clip and total(abs(!map.ll_box)) eq 0 then $ ; Try to make a lon/lat range
  929.   map_set_ll_box
  930.  
  931. if keyword_set(noborder) eq 0 then  $ ;Draw the border.
  932.   plots, !x.window[[0,1,1,0,0]], !y.window[[0,0,1,1,0]], $
  933.      COLOR=color, zvalue, /NORM, /NOCLIP, T3D=t3d
  934.  
  935. ; Collect the common graphics keywords
  936. map_struct_append, egraphics, "COLOR", color
  937. map_struct_append, egraphics, "T3D", t3d
  938. map_struct_append, egraphics, "ZVALUE", zvalue
  939.  
  940. ; Process MAP_HORIZON keywords:    **************
  941. map_struct_merge, ehorizon, egraphics ;Add common graphics keywords
  942. if keyword_set(horizon) then MAP_HORIZON, _EXTRA=ehorizon
  943.  
  944. ; Process MAP_CONTINENT keywords:    **************
  945. if n_elements(mlinestyle) then map_struct_append, econt, "LINESTYLE", mlinestyle
  946. if n_elements(mlinethick) then map_struct_append, econt, "THICK", mlinethick
  947. if n_elements(con_color) then map_struct_append, econt, "COLOR", con_color
  948. if n_elements(hires) then map_struct_append, econt, "HIRES", hires
  949. if n_elements(continents) then map_struct_append, econt, "CONTINENTS", continents
  950. if n_elements(usa) then map_struct_append, econt, "USA", usa
  951. if n_elements(econt) gt 0 or keyword_set(continents) then begin
  952.     map_struct_merge, econt, egraphics ;Add common graphics kwrds
  953.     MAP_CONTINENTS, _EXTRA=econt
  954.     endif
  955.  
  956. ; Process MAP_GRID keywords:    **************
  957. if n_elements(label)  then map_struct_append, egrid, "LABEL", label
  958. if n_elements(latlab) then map_struct_append, egrid, "LATLAB", latlab
  959. if n_elements(lonlab) then map_struct_append, egrid, "LONLAB", lonlab
  960. if n_elements(latdel) then map_struct_append, egrid, "LATDEL", latdel
  961. if n_elements(londel) then map_struct_append, egrid, "LONDEL", londel
  962. if n_elements(latalign) then map_struct_append, egrid, "LATALIGN", latalign
  963. if n_elements(lonalign) then map_struct_append, egrid, "LONALIGN", lonalign
  964. do_grid = (keyword_set(grid) + n_elements(egrid) + n_elements(glinestyle) + $
  965.            n_elements(glinethick)) ne 0
  966.  
  967. map_struct_merge, egrid, egraphics
  968. if n_elements(glinestyle) then $ ;These keywords supersede those in egraphics
  969.   map_struct_append, egrid, "LINESTYLE", glinestyle
  970. if n_elements(glinethick) then $
  971.   map_struct_append, egrid, "THICK", glinethick
  972. if do_grid then MAP_GRID, CHARSIZE=charsize, _EXTRA=egrid
  973.  
  974. if KEYWORD_SET(ADVANCE) and !P.Multi[0] gt 0 THEN $
  975.      !P.Multi[0] = !P.Multi[0] - 1 $
  976. else !p.multi[0] = !p.multi[1] * !p.multi[2] - 1
  977.  
  978. end
  979.